<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title>Three.js - Custom Geometry - Heightmap</title>
    <style>
    html, body {
        height: 100%;
        margin: 0;
    }
    #c {
        width: 100%;
        height: 100%;
        display: block;
    }
    </style>
  </head>
  <body>
    <canvas id="c"></canvas>
  </body>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

<script type="importmap">
{
  "imports": {
    "three": "../../build/three.module.js",
    "three/addons/": "../../examples/jsm/"
  }
}
</script>

<script type="module">
import * as THREE from 'three';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 200;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(20, 20, 20);

  const controls = new OrbitControls(camera, canvas);
  controls.target.set(0, 0, 0);
  controls.update();

  const scene = new THREE.Scene();

  function addLight(...pos) {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(...pos);
    scene.add(light);
  }

  addLight(-1, 2, 4);
  addLight(1, 2, -2);

  const imgLoader = new THREE.ImageLoader();
  imgLoader.load('resources/images/heightmap-96x64.png', createHeightmap);

  function createHeightmap(image) {
    // extract the data from the image by drawing it to a canvas
    // and calling getImageData
    const ctx = document.createElement('canvas').getContext('2d');
    const {width, height} = image;
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    ctx.drawImage(image, 0, 0);
    const {data} = ctx.getImageData(0, 0, width, height);

    const geometry = new THREE.Geometry();

    const cellsAcross = width - 1;
    const cellsDeep = height - 1;
    for (let z = 0; z < cellsDeep; ++z) {
      for (let x = 0; x < cellsAcross; ++x) {
        // compute row offsets into the height data
        // we multiply by 4 because the data is R,G,B,A but we
        // only care about R
        const base0 = (z * width + x) * 4;
        const base1 = base0 + (width * 4);

        // look up the height for the for points
        // around this cell
        const h00 = data[base0] / 32;
        const h01 = data[base0 + 4] / 32;
        const h10 = data[base1] / 32;
        const h11 = data[base1 + 4] / 32;
        // compute the average height
        const hm = (h00 + h01 + h10 + h11) / 4;

        // the corner positions
        const x0 = x;
        const x1 = x + 1;
        const z0 = z;
        const z1 = z + 1;

        // remember the first index of these 5 vertices
        const ndx = geometry.vertices.length;

        // add the 4 corners for this cell and the midpoint
        geometry.vertices.push(
          new THREE.Vector3(x0, h00, z0),
          new THREE.Vector3(x1, h01, z0),
          new THREE.Vector3(x0, h10, z1),
          new THREE.Vector3(x1, h11, z1),
          new THREE.Vector3((x0 + x1) / 2, hm, (z0 + z1) / 2),
        );

        //      2----3
        //      |\  /|
        //      | \/4|
        //      | /\ |
        //      |/  \|
        //      0----1

        // create 4 triangles
        geometry.faces.push(
          new THREE.Face3(ndx    , ndx + 4, ndx + 1),
          new THREE.Face3(ndx + 1, ndx + 4, ndx + 3),
          new THREE.Face3(ndx + 3, ndx + 4, ndx + 2),
          new THREE.Face3(ndx + 2, ndx + 4, ndx + 0),
        );

        // add the texture coordinates for each vertex of each face.
        const u0 = x / cellsAcross;
        const v0 = z / cellsDeep;
        const u1 = (x + 1) / cellsAcross;
        const v1 = (z + 1) / cellsDeep;
        const um = (u0 + u1) / 2;
        const vm = (v0 + v1) / 2;
        geometry.faceVertexUvs[0].push(
          [ new THREE.Vector2(u0, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v0) ],
          [ new THREE.Vector2(u1, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v1) ],
          [ new THREE.Vector2(u1, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v1) ],
          [ new THREE.Vector2(u0, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v0) ],
        );
      }
    }

    geometry.computeFaceNormals();

    // center the geometry
    geometry.translate(width / -2, 0, height / -2);

    const loader = new THREE.TextureLoader();
    const texture = loader.load('resources/images/star.png');

    const material = new THREE.MeshPhongMaterial({color: 'green', map: texture});

    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
  }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render() {

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>
</html>

